# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: chensili

import abc
import logging
import os

from coin.exchange.base.order_gateway import OrderSide
from coin.strategy.executor.util.working_order_state import (WorkingOrderStateGenerator)
from coin.strategy.executor.util.logger import order_logger
from coin.proto.coin_executor_pb2 import (AGGRESSIVE_EXECUTOR, PASSIVE_EXECUTOR)


class ExecutorTraitsBase(object):
  BuyOrderSide = None
  SellOrderSide = None

  def __init__(self, config2, order_gateway, product, logger=None):
    self._logger = logger or logging.getLogger('ExecutorTraitsBase')
    self._config2 = config2
    config_type = type(config2.config)
    if config_type.__name__ == "AggressiveExecutorConfig":
      self._executor_type = AGGRESSIVE_EXECUTOR
    elif config_type.__name__ == "PassiveExecutorConfig":
      self._executor_type = PASSIVE_EXECUTOR
    else:
      raise NotImplementedError(config_type)
    self._og = order_gateway
    self._product = product

    self._check_config(self.config)

  @property
  def config(self):
    # config = constant config
    # config2 = adjustable config
    return self._config2.config

  @property
  def config2(self):
    # config = constant config
    # config2 = adjustable config
    return self._config2

  def _on_exit(self):
    self.og.cancel_all()
    self.logger.info('Orders canceled before exiting executor.')
    os._exit(1)

  def _check_state_general(self):
    fill_stats = self.og.get_fill_stats(self.product)
    pos_range = self.config2.pos_range
    pos_buffer = 2 * self.config2.lot_size
    acceptable_pos_range = min(pos_range * 1.2, pos_range + pos_buffer)
    net_fill_from_order = abs(fill_stats.order.net_fill)
    net_fill_from_trade = abs(fill_stats.trade.net_fill)
    if net_fill_from_order > acceptable_pos_range:
      self.logger.error(
          'Exit executor because net fill from order greater than acceptable pos range. %s: %s vs %s'
          % (self.product, net_fill_from_order, acceptable_pos_range))
      self._on_exit()
    if net_fill_from_trade > acceptable_pos_range:
      self.logger.error(
          'Exit executor because net fill from trade greater than acceptable pos range. %s: %s vs %s'
          % (self.product, net_fill_from_trade, acceptable_pos_range))
      self._on_exit()
    if fill_stats.order.consecutive_fill_qty > acceptable_pos_range:
      self.logger.error(
          'Exit executor because consecutive fill qty from order greater than acceptable pos range. %s: %s vs %s'
          % (self.product, fill_stats.order.consecutive_fill_qty, acceptable_pos_range))
      self._on_exit()
    if fill_stats.trade.consecutive_fill_qty > acceptable_pos_range:
      self.logger.error(
          'Exit executor because consecutive fill qty from trade greater than acceptable pos range. %s: %s vs %s'
          % (self.product, fill_stats.trade.consecutive_fill_qty, acceptable_pos_range))
      self._on_exit()

  @property
  def product(self):
    return self._product

  @property
  def og(self):
    return self._og

  @property
  def logger(self):
    return self._logger

  def _cancel_orders(self, order_id_list):
    assert isinstance(order_id_list, (list, tuple))
    self._cancel_orders_impl(list(order_id_list))

  @abc.abstractmethod
  def _check_config(self, config):
    raise NotImplementedError()

  @abc.abstractmethod
  def _get_position(self, product):
    raise NotImplementedError()

  @abc.abstractmethod
  def _submit_order(self, product, price, qty, order_side):
    raise NotImplementedError()

  # A normal order will be sent for an exchange which does not support
  # post_only.
  def _submit_pass_order(self, product, price, qty, order_side):
    return self._submit_order(product, price, qty, order_side)

  def _amend_order(self, order_id, product, price, qty, order_side):
    raise NotImplementedError()

  @abc.abstractmethod
  def _cancel_orders_impl(self, order_id_list):
    raise NotImplementedError()

  @abc.abstractmethod
  def _check_state_impl(self):
    raise NotImplementedError()


class SpotExecutorTraits(ExecutorTraitsBase):
  BuyOrderSide = [OrderSide.BUY]
  SellOrderSide = [OrderSide.SELL]

  def _get_position(self, product):
    return self.og.get_balance(product.base.currency)

  def _check_config(self, config):
    if type(config).__name__ == "AggressiveExecutorConfig":
      from coin.strategy.executor.aggressive.util import check_spot_executor_config
      check_spot_executor_config(config)
    elif type(config).__name__ == "PassiveExecutorConfig":
      from coin.strategy.executor.passive.util import check_spot_executor_config
      check_spot_executor_config(config)
    else:
      raise NotImplementedError(type(config))

  def _check_state_impl(self):
    pass


class MarginExecutorTraits(ExecutorTraitsBase):
  def _get_position(self, product):
    return self.og.get_position(product)

  def _check_config(self, config):
    if type(config).__name__ == "AggressiveExecutorConfig":
      from coin.strategy.executor.aggressive.util import check_executor_config
      check_executor_config(config)
    elif type(config).__name__ == "PassiveExecutorConfig":
      from coin.strategy.executor.passive.util import check_executor_config
      check_executor_config(config)
    else:
      raise NotImplementedError(type(config))

  def _check_state_impl(self):
    pass


class FuturesExecutorTraits(ExecutorTraitsBase):
  def _get_position(self, product):
    return self.og.get_position(product)

  def _check_config(self, config):
    if type(config).__name__ == "AggressiveExecutorConfig":
      from coin.strategy.executor.aggressive.util import check_executor_config
      check_executor_config(config)
    elif type(config).__name__ == "PassiveExecutorConfig":
      from coin.strategy.executor.passive.util import check_executor_config
      check_executor_config(config)
    else:
      raise NotImplementedError(type(config))

  def _check_state_impl(self):
    pass
